/* Copyright (C) 2012  	Mathieu Marleix <mathieu.marleix@aerian.fr>
 *						Doris Vaton <doris.vaton@gmail.com>
 * 
 * Based on a file of Takashi Kanamaru <kanamaru@cc.kogakuin.ac.jp>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.shaipicku.mjpegcams.mjpegView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.shaipicku.mjpegcams.LogCat;
import com.shaipicku.mjpegcams.MjpegCamActivity;
import com.shaipicku.mjpegcams.R;
import com.shaipicku.mjpegcams.ShapeData;

/**
 * The Display of a given IP camera (Mjpeg)
 * @author Mathieu Marleix <mathieu.marleix@aerian.fr>
 * @author Doris Vaton <doris.vaton@gmail.com>
 *
 */
public class MjpegView extends SurfaceView implements SurfaceHolder.Callback {

	public static final String TAG="MJPEG";
	private boolean DEBUG = false;
	private String MODE;
	private boolean ScreenDisplay = false;
	private int camId;

	public final static int POSITION_UPPER_LEFT  = 9;
	public final static int POSITION_UPPER_RIGHT = 3;
	public final static int POSITION_LOWER_LEFT  = 12;
	public final static int POSITION_LOWER_RIGHT = 6;

	public final static int SIZE_STANDARD   = 1; 
	public final static int SIZE_BEST_FIT   = 4;
	public final static int SIZE_FULLSCREEN = 8;

	public int LEFT[] = {-1,-1}, MIDDLE[] = {-1,-1}, RIGHT[] = {-1,-1};

	SurfaceHolder holder = null;
	Context saved_context = null;

	private MjpegCamActivity mjpeg2 = null;
	private MjpegViewThread thread = null;
	private MjpegInputStream mIn = null;   
	private boolean showFps = false;
	private boolean mRun = false;
	private boolean surfaceDone = false; 
	private static boolean updateConfiguration = false;    

	private Paint overlayPaint = null;
	private int overlayTextColor = -1;
	private int overlayBackgroundColor = -1;
	private int ovlPos = -1;
	private int dispWidth = -1;
	private int dispHeight = -1;

	private List<ShapeData> listOfShapeRed, listOfShapeBlue, listOfShapeGreen;//, listOfShapeYellow;

	/**
	 * Nested class which set the content of the MjpegView
	 * @author Mathieu Marleix <mathieu.marleix@aerian.fr>
	 * @author Doris Vaton <doris.vaton@gmail.com>
	 *
	 */
	public class MjpegViewThread extends Thread {
		ProgressDialog mProgressDialog = null;
		private final Exception Exception = null;
		private SurfaceHolder mSurfaceHolder;
		private int frameCounter = 0;
		private long start = -1;
		private String fps = "";
		private boolean firstPassage = true;
		private Bitmap bm=null;
		private Mat mImg = null;
		private Scalar Color = null;

		public MjpegViewThread(SurfaceHolder surfaceHolder, Context context) { 
			mSurfaceHolder = surfaceHolder; 
		}

		private Rect destRect(int bmw, int bmh) {
			if(DEBUG && firstPassage){ 
				Log.d(TAG,"Size: " + getDispWidth() +"x"+ getDispHeight()+"\n");
				LogCat.writeLog(saved_context,  this.getName() + " Size: " + getDispWidth() +"x"+ getDispHeight());
			}
			return new Rect(0, 0, getDispWidth(), getDispHeight());

		}

		public void setSurfaceSize(int width, int height) {
			synchronized(mSurfaceHolder) {
				setDispWidth(width);
				setDispHeight(height);
			}
		}
		/**
		 * Display a counter of frame per second at the right bottom of the output bitmap.
		 * @param p	Paint
		 * @return bm Bitmap, the output bitmap.
		 */
		private Bitmap makeFpsOverlay(Paint p) {
			Rect b = new Rect();
			p.getTextBounds(fps, 0, fps.length(), b);

			// false indentation to fix forum layout             
			Bitmap bm = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);

			Canvas c = new Canvas(bm);
			p.setColor(overlayBackgroundColor);
			c.drawRect(0, 0, b.width(), b.height(), p);
			p.setColor(overlayTextColor);
			c.drawText(fps, -b.left, b.bottom-b.top-p.descent(), p);
			return bm;        	 
		}
		/**
		 * Function which set the content of the view extracted from the MjpegInputStream.
		 */
		public void run() {
			start = System.currentTimeMillis();
			PorterDuffXfermode mode = new PorterDuffXfermode(PorterDuff.Mode.DST_OVER);

			int width;
			int height;
			boolean guessing_form = false;
			Paint p = new Paint();
			Bitmap ovl=null;
			int detectionCeil = 0;
			setmImg(new Mat());
			
			while (mRun) {

				Rect destRect=null;
				Canvas canvas = null;

				if(surfaceDone) {   
					try {
						if(mIn == null){
							setmRun(false);
							throw Exception;
						}
						else {
							//Get the bitmap of MjpegInputStream
							while(getBm() == null)
								setBm(mIn.readMjpegFrame());
							//Adapt the size of the image to fit the screen size
							setDispHeight(getBm().getHeight()*getDispWidth()/getBm().getWidth());
							setBm(Bitmap.createScaledBitmap(getBm(), getDispWidth(), getDispHeight(), true));
							if(DEBUG && firstPassage){
								Log.d(TAG,"bm config: " + getBm().getConfig() + "\n");
								LogCat.writeLog(saved_context, "bm config: " + getBm().getConfig() + "\n");
							}

							if (isUpdateConfiguration()){
								detectionCeil = 0;
								getListOfShapeBlue().clear();
								getListOfShapeGreen().clear();
								getListOfShapeRed().clear();
								//								getListOfShapeYellow().clear();
							}

							
							if (isScreenDisplay()|| (!getMODE().equals("CAMERA") && detectionCeil < 20)){ 
								guessing_form = true;

								if(!isScreenDisplay() && mProgressDialog == null && isUpdateConfiguration() || mProgressDialog != null && !isScreenDisplay() && !mProgressDialog.isShowing() && isUpdateConfiguration()){
									getMjpeg2().runOnUiThread(new Runnable() {
										@Override
										public void run() {
											mProgressDialog = ProgressDialog.show(getMjpeg2(),getMjpeg2().getString(R.string.cam2),getMjpeg2().getString(R.string.data_acquiring) + " " + getCamId(),true);
											mProgressDialog.setCancelable(true);
										}
									});
								}

								//Transform the bitmap of the image in matrix
								Utils.bitmapToMat(getBm(),getmImg());
								//For the detection of the contour you need to have CV_8UC1 matrix
								Mat mIntermediateMat = new Mat(getmImg().rows(), getmImg().cols(), CvType.CV_8UC1);
								//Red detection
								ShapeData sr = shapeDetection(mIntermediateMat,new Scalar(110,100,30),new Scalar(130,255,255),new Size(6,6),ShapeData.RED);
								//Green detection
								ShapeData sg = shapeDetection(mIntermediateMat,new Scalar(40,40,20),new Scalar(85,255,255),new Size(3,3),ShapeData.GREEN);
								//Blue detection need light for optimal result
								ShapeData sb = shapeDetection(mIntermediateMat,new Scalar(0,100,15),new Scalar(50,255,255),new Size(5,5),ShapeData.BLUE);
								//								ShapeData sy = shapeDetection(mIntermediateMat,new Scalar(80,60,15),new Scalar(110,255,255),new Size(6,6),ShapeData.YELLOW);
								//Print information on the screen
								if(isScreenDisplay()){

									switch (sr.getShape()) {
									case ShapeData.CIRCLE:
										Core.putText(mImg, saved_context.getString(R.string.circle_shape)  + " " + sr.getX() + ":" + sr.getY(), new Point(sr.getX(), sr.getY()), Core.FONT_ITALIC, 1, new Scalar(255, 0, 0, 255));
										break;
									case ShapeData.SQUARE:
										Core.putText(mImg, saved_context.getString(R.string.square_shape)  + " " + sr.getX() + ":" + sr.getY(), new Point(sr.getX(), sr.getY()), Core.FONT_ITALIC, 1, new Scalar(255, 0, 0, 255));
										break;
									case ShapeData.TRIANGLE:
										Core.putText(mImg, saved_context.getString(R.string.triangle_shape)  + " " + sr.getX() + ":" + sr.getY(), new Point(sr.getX(), sr.getY()), Core.FONT_ITALIC, 1, new Scalar(255, 0, 0, 255));
										break;
									default:
										break;
									}

									switch (sg.getShape()) {
									case ShapeData.CIRCLE:
										Core.putText(mImg, saved_context.getString(R.string.circle_shape) + " " + sg.getX() + ":" + sr.getY() , new Point(sg.getX(), sg.getY()), Core.FONT_ITALIC, 1, new Scalar(0, 255, 0, 255));
										break;
									case ShapeData.SQUARE:
										Core.putText(mImg, saved_context.getString(R.string.square_shape) + " " + sg.getX() + ":" + sg.getY()  , new Point(sg.getX(), sg.getY()), Core.FONT_ITALIC, 1, new Scalar(0, 255, 0, 255));
										break;
									case ShapeData.TRIANGLE:
										Core.putText(mImg, saved_context.getString(R.string.triangle_shape) + " " + sg.getX() + ":" + sg.getY()  , new Point(sg.getX(), sg.getY()), Core.FONT_ITALIC, 1, new Scalar(0, 255, 0, 255));
										break;
									default:
										break;
									}

									switch (sb.getShape()) {
									case ShapeData.CIRCLE:
										Core.putText(mImg, saved_context.getString(R.string.circle_shape) + " " + sb.getX() + ":" + sb.getY() , new Point(sb.getX(), sb.getY()), Core.FONT_ITALIC, 1, new Scalar(0, 0, 255, 255));
										break;
									case ShapeData.SQUARE:
										Core.putText(mImg, saved_context.getString(R.string.square_shape) + " " + sb.getX() + ":" + sb.getY()  , new Point(sb.getX(), sb.getY()), Core.FONT_ITALIC, 1, new Scalar(0, 0, 255, 255));
										break;
									case ShapeData.TRIANGLE:
										Core.putText(mImg, saved_context.getString(R.string.triangle_shape) + " " + sb.getX() + ":" + sb.getY()  , new Point(sb.getX(), sb.getY()), Core.FONT_ITALIC, 1, new Scalar(0, 0, 255, 255));
										break;
									default:
										break;
									}

									//									switch (sy.getShape()) {
									//									case ShapeData.CIRCLE:
									//										Core.putText(mImg, saved_context.getString(R.string.circle_shape) + " " + sy.getX() + ":" + sy.getY() , new Point(sy.getX(), sy.getY()), Core.FONT_ITALIC, 1, new Scalar(255, 255, 0, 255));
									//										break;
									//									case ShapeData.SQUARE:
									//										Core.putText(mImg, saved_context.getString(R.string.square_shape) + " " + sy.getX() + ":" + sy.getY()  , new Point(sy.getX(), sy.getY()), Core.FONT_ITALIC, 1, new Scalar(255, 255, 0, 255));
									//										break;
									//									case ShapeData.TRIANGLE:
									//										Core.putText(mImg, saved_context.getString(R.string.triangle_shape) + " " + sy.getX() + ":" + sy.getY()  , new Point(sy.getX(), sy.getY()), Core.FONT_ITALIC, 1, new Scalar(255, 255, 0, 255));
									//										break;
									//									default:
									//										break;
									//									}
									//Transformation of the matrix in bitmap
									Utils.matToBitmap(getmImg(), getBm());
								}

								mIntermediateMat.release();
								mIntermediateMat = null;
								
								//Remove elements if there is more than 6 elements
								if(getListOfShapeBlue().size() > 6)
									((LinkedList<ShapeData>) getListOfShapeBlue()).removeFirst();
								if(getListOfShapeGreen().size() > 6)
									((LinkedList<ShapeData>) getListOfShapeGreen()).removeFirst();
								if(getListOfShapeRed().size() > 6)
									((LinkedList<ShapeData>) getListOfShapeRed()).removeFirst();
								//								if(getListOfShapeYellow().size() > 6)
								//									getListOfShapeYellow().remove(0);
								
								//Add the elements well detected
								if(sb.getShape() != ShapeData.NOTHING && sb.getColor() != ShapeData.NOTHING)
									((LinkedList<ShapeData>) getListOfShapeBlue()).addLast(sb);
								if(sg.getShape() != ShapeData.NOTHING && sg.getColor() != ShapeData.NOTHING)
									((LinkedList<ShapeData>) getListOfShapeGreen()).addLast(sg);
								if(sr.getShape() != ShapeData.NOTHING && sr.getColor() != ShapeData.NOTHING)
									((LinkedList<ShapeData>) getListOfShapeRed()).addLast(sr);
								//								if(sy.getColor() != ShapeData.NOTHING)
								//									getListOfShapeYellow().add(sy);
								detectionCeil++;
								setUpdateConfiguration(false);
							}
							else{
								guessing_form = false;
							}
							if((!getMODE().equals("CAMERA"))  && detectionCeil >= 20 && getListOfShapeBlue().size() != 0 && getListOfShapeGreen().size() != 0 && getListOfShapeRed().size() != 0){ 
								mapShapes();
							}
							
							//Actualization of the button in other mode than Pile Up
							if(!guessing_form && !getMODE().equals("PILE_UP")){
								//Change colors of the slider's buttons
								changeButtonMjeg2(ShapeData.RED);
								changeButtonMjeg2(ShapeData.BLUE);
								changeButtonMjeg2(ShapeData.GREEN);
								getMjpeg2().getsDrawer().unlock();
							}
							else if(getMODE().equals("PILE_UP")){
								//Change colors of the slider's buttons
								if(getMjpeg2().isLeftPresent() && getMjpeg2().isMiddlePresent() && getMjpeg2().isRightPresent()){
									changeButtonMjeg2(ShapeData.RED);
									changeButtonMjeg2(ShapeData.BLUE);
									changeButtonMjeg2(ShapeData.GREEN);
								}
								getMjpeg2().getsDrawer().unlock();
							}
							//							getMjpeg2().setCenterList(getCamId(),getListOfShapeYellow(), new int[]{getDispHeight(),getDispHeight()});
						} 
						if(guessing_form == false && mProgressDialog != null){
							mProgressDialog.dismiss();
						}
						destRect = destRect(getDispWidth(), getDispHeight());
					}
					catch (IOException e){
						e.printStackTrace();
					}
					catch (Exception e){
						e.printStackTrace();
					}

					try {
						canvas = mSurfaceHolder.lockCanvas();
						synchronized (mSurfaceHolder) {
							if(getBm() == null){
								throw Exception;
							}
							else{
								canvas.drawBitmap(getBm(),0,0,null);

								if(DEBUG && firstPassage){
									Log.d(TAG,"Canvas 1 : " + canvas.getWidth() + "x" + canvas.getHeight()+ " Image : " + getBm().getWidth() + "x" + getBm().getHeight() + "\n");
									LogCat.writeLog(saved_context, "Canvas 1 : " + canvas.getWidth() + "x" + canvas.getHeight()+ " Image : " + getBm().getWidth() + "x" + getBm().getHeight() + "\n");
									firstPassage = false;
								}

							}

							if(showFps) {
								p.setXfermode(mode);
								if(ovl != null) {

									// false indentation to fix forum layout 	                                	 
									height = ((ovlPos & 1) == 1) ? destRect.top : destRect.bottom-ovl.getHeight();
									width  = ((ovlPos & 8) == 8) ? destRect.left : destRect.right -ovl.getWidth();

									canvas.drawBitmap(ovl, width, height, null);
								}
								p.setXfermode(null);
								frameCounter++;
								if((System.currentTimeMillis() - start) >= 1000) {
									fps = String.valueOf(frameCounter)+"fps";
									frameCounter = 0; 
									start = System.currentTimeMillis();
									if(ovl!=null) ovl.recycle();

									ovl = makeFpsOverlay(overlayPaint);
								}
							}
						}
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} finally { 
						if (canvas != null) mSurfaceHolder.unlockCanvasAndPost(canvas); 
						if(getBm()!=null) getBm().recycle();
						setBm(null);
					}
				}
			}
			//Matrix destruction
			mImg.release();
			mImg = null;
		}
		/**
		 * Set the current order of the shape on the game board
		 */
		private void mapShapes(){
			int coordinateBlue[] = {0,0}, coordinateRed[] = {0,0}, coordinateGreen[] = {0,0}, sumOfCoordinate = 0;
			int square = 0, triangle = 0, circle = 0, blueShape = -1, redShape = -1, greenShape = -1, max = -1;
			
			//Detect the most present element in the list in order to avoid bad detection
			for(ShapeData d : getListOfShapeBlue()){
				coordinateBlue[0] += d.getX();
				coordinateBlue[1] += d.getY();
				switch (d.getShape()) {
				case ShapeData.SQUARE:
					square++;
					break;
				case ShapeData.TRIANGLE:
					triangle++;
					break;
				case ShapeData.CIRCLE:
					circle++;
					break;
				default:
					break;
				}
				sumOfCoordinate++;
			}

			max = Math.max(square, triangle);
			max = Math.max(max,circle);
			if(max == triangle)
				blueShape = ShapeData.TRIANGLE;
			else if(square == max)
				blueShape = ShapeData.SQUARE;
			else
				blueShape = ShapeData.CIRCLE;

			if(sumOfCoordinate != 0){
				coordinateBlue[0] = coordinateBlue[0]/sumOfCoordinate;
				coordinateBlue[1] = coordinateBlue[1]/sumOfCoordinate;
			}
			sumOfCoordinate = circle = triangle = square = 0;

			for(ShapeData d : getListOfShapeRed()){
				coordinateRed[0] += d.getX();
				coordinateRed[1] += d.getY();
				switch (d.getShape()) {
				case ShapeData.SQUARE:
					square++;
					break;
				case ShapeData.TRIANGLE:
					triangle++;
					break;
				case ShapeData.CIRCLE:
					circle++;
					break;
				default:
					break;
				}
				sumOfCoordinate++;
			}
			max = Math.max(square, triangle);
			max = Math.max(max,circle);
			if(max == triangle)
				redShape = ShapeData.TRIANGLE;
			else if(square == max)
				redShape = ShapeData.SQUARE;
			else
				redShape = ShapeData.CIRCLE;

			if(sumOfCoordinate != 0){
				coordinateRed[0] = coordinateRed[0]/sumOfCoordinate;
				coordinateRed[1] = coordinateRed[1]/sumOfCoordinate;
			}
			sumOfCoordinate = circle = triangle = square = 0;

			for(ShapeData d : getListOfShapeGreen()){
				coordinateGreen[0] += d.getX();
				coordinateGreen[1] += d.getY();
				switch (d.getShape()) {
				case ShapeData.SQUARE:
					square++;
					break;
				case ShapeData.TRIANGLE:
					triangle++;
					break;
				case ShapeData.CIRCLE:
					circle++;
					break;
				default:
					break;
				}
				sumOfCoordinate++;
			}
			max = Math.max(square, triangle);
			max = Math.max(max,circle);
			if(max == triangle)
				greenShape = ShapeData.TRIANGLE;
			else if(square == max)
				greenShape = ShapeData.SQUARE;
			else
				greenShape = ShapeData.CIRCLE;

			if(sumOfCoordinate != 0){
				coordinateGreen[0] = coordinateGreen[0]/sumOfCoordinate;
				coordinateGreen[1] = coordinateGreen[1]/sumOfCoordinate;
			}
			
			//Set the place of the shape
			if(coordinateGreen[0] > coordinateRed[0] && coordinateGreen[0] > coordinateBlue[0]){
				RIGHT[0] = ShapeData.GREEN;
				RIGHT[1] = greenShape;
				if(coordinateBlue[0] > coordinateRed[0]){
					MIDDLE[0] = ShapeData.BLUE;
					MIDDLE[1] = blueShape;
					LEFT[0] = ShapeData.RED;
					LEFT[1] = redShape;
				}
				else{
					MIDDLE[0] = ShapeData.RED;
					MIDDLE[1] = redShape;
					LEFT[0] = ShapeData.BLUE;
					LEFT[1] = blueShape;
				}
			}
			else if(coordinateRed[0] > coordinateGreen[0] && coordinateRed[0] > coordinateBlue[0]){
				RIGHT[0] = ShapeData.RED;
				RIGHT[1] = redShape;
				if(coordinateGreen[0] > coordinateBlue[0]){
					MIDDLE[0] = ShapeData.GREEN;
					MIDDLE[1] = greenShape;
					LEFT[0] = ShapeData.BLUE;
					LEFT[1] = blueShape;
				}
				else{
					MIDDLE[0] = ShapeData.BLUE;
					MIDDLE[1] = blueShape;
					LEFT[0]  = ShapeData.GREEN;
					LEFT[1] = greenShape;
				}
			}
			else if(coordinateBlue[0] > coordinateGreen[0] && coordinateBlue[0] > coordinateRed[0]){
				RIGHT[0] = ShapeData.BLUE;
				RIGHT[1] = blueShape;
				if(coordinateGreen[0] > coordinateRed[0]){
					MIDDLE[0] = ShapeData.GREEN;
					MIDDLE[1] = greenShape;
					LEFT[0] = ShapeData.RED;
					LEFT[1] = redShape;
				}
				else{
					MIDDLE[0] = ShapeData.RED;
					MIDDLE[1] = redShape;
					LEFT[0] = ShapeData.GREEN;
					LEFT[1] = greenShape;
				}
			}
			if(DEBUG)
				Log.d(TAG, "Configuration of the environnement n. " + getCamId() + ": left " + ShapeData.getLabel(LEFT[0]) + "/" + ShapeData.getLabel(LEFT[1]) + " middle " + ShapeData.getLabel(MIDDLE[0]) + "/" + ShapeData.getLabel(MIDDLE[1]) + " right " + ShapeData.getLabel(RIGHT[0]) + "/"  + ShapeData.getLabel(RIGHT[1]) +"\n");
		}
		/**
		 * Launch the change of shape buttons displayed on the screen
		 * @param integer color
		 */
		private void changeButtonMjeg2(int color) {
			// TODO Auto-generated method stub
			int squareNumber = 0, triangleNumber = 0, circleNumber = 0, nothingNumber = 0, max = 0, button = 0;

			switch (color) {
			case ShapeData.RED:
				if(LEFT[0] == ShapeData.RED)
					button = R.id.button1;
				else if(MIDDLE[0] == ShapeData.RED)
					button = R.id.button2;
				else if(RIGHT[0] == ShapeData.RED)
					button = R.id.button3;
				else button = R.id.button1;
				
				for(ShapeData sd : getListOfShapeRed()){
					if(sd.getShape() == ShapeData.CIRCLE)
						circleNumber++;
					else if(sd.getShape() == ShapeData.TRIANGLE)
						triangleNumber++;
					else if(sd.getShape() == ShapeData.SQUARE)
						squareNumber++;
					else 
						nothingNumber++;
				}
				break;
			case ShapeData.BLUE:
				if(LEFT[0] == ShapeData.BLUE)
					button = R.id.button1;
				else if(MIDDLE[0] == ShapeData.BLUE)
					button = R.id.button2;
				else if(RIGHT[0] == ShapeData.BLUE)
					button = R.id.button3;
				else button = R.id.button2;
				
				for(ShapeData sd : getListOfShapeBlue()){
					if(sd.getShape() == ShapeData.CIRCLE)
						circleNumber++;
					else if(sd.getShape() == ShapeData.TRIANGLE)
						triangleNumber++;
					else if(sd.getShape() == ShapeData.SQUARE)
						squareNumber++;
					else 
						nothingNumber++;
				}
				break;
			case ShapeData.GREEN:
				if(LEFT[0] == ShapeData.GREEN)
					button = R.id.button1;
				else if(MIDDLE[0] == ShapeData.GREEN)
					button = R.id.button2;
				else if(RIGHT[0] == ShapeData.GREEN)
					button = R.id.button3;
				else button = R.id.button3;
				
				for(ShapeData sd : getListOfShapeGreen()){
					if(sd.getShape() == ShapeData.CIRCLE)
						circleNumber++;
					else if(sd.getShape() == ShapeData.TRIANGLE)
						triangleNumber++;
					else if(sd.getShape() == ShapeData.SQUARE)
						squareNumber++;
					else 
						nothingNumber++;
				}
				break;

			default:
				break;
			}
			if(squareNumber == 0 && triangleNumber == 0 && circleNumber == 0 && nothingNumber == 0)
				getMjpeg2().setButtonBackground(new ShapeData(ShapeData.NOTHING,ShapeData.NOTHING, 0, 0),getCamId(),button);
			else{
				max = Math.max(squareNumber, triangleNumber);
				max = Math.max(max, circleNumber);
				max = Math.max(max, nothingNumber);
				if(max == nothingNumber && nothingNumber != 0)
					getMjpeg2().setButtonBackground(new ShapeData(ShapeData.NOTHING,ShapeData.NOTHING, 0, 0),getCamId(),button);
				else if(max == squareNumber && squareNumber != 0)
					getMjpeg2().setButtonBackground(new ShapeData(color,ShapeData.SQUARE, 0, 0),getCamId(),button);
				else if(max == triangleNumber && triangleNumber != 0)
					getMjpeg2().setButtonBackground(new ShapeData(color,ShapeData.TRIANGLE, 0, 0),getCamId(),button);
				else if(max == circleNumber && circleNumber != 0)
					getMjpeg2().setButtonBackground(new ShapeData(color,ShapeData.CIRCLE, 0, 0),getCamId(),button);
			}
		}

		// Setters and getters of Bitmap bm and Matrix mImg
		/**
		 * @return Bitmap bm
		 */
		public Bitmap getBm() {
			return bm;
		}

		/**
		 * @param bm The bitmap to set
		 */
		public void setBm(Bitmap bm) {
			this.bm = bm;
		}

		/**
		 * @return Matrix mImg
		 */
		public Mat getmImg() {
			return mImg;
		}

		/**
		 * @param matrix mImg
		 */
		public void setmImg(Mat mImg) {
			this.mImg = mImg;
		}

		/**
		 * Perform a shape detection of circle, triangle and square.
		 * @param Matrix mIntermediateMat 	An OpenCV matrix. 
		 * @param Scalar lower				The lower boundary of the color to detect in HSV. 
		 * @param Scalar upper				The upper boundary of the color to detect in HSV. 
		 * @param Size kernelSize			The size of the kernel matrix for the opening on the image.
		 * @param Scalar SColor				The color for the form drawn on the screen. 
		 */
		private ShapeData shapeDetection(Mat mIntermediateMat,Scalar lower, Scalar upper, Size kernelSize, int Color){
			Scalar SColor;
			ShapeData sd;
			if(Color == ShapeData.RED){
				SColor = new Scalar(255, 0, 0, 255);
				sd = new ShapeData(ShapeData.RED, ShapeData.NOTHING, ShapeData.NOTHING, ShapeData.NOTHING);
			}
			else if(Color == ShapeData.GREEN){
				SColor = new Scalar(0, 255, 0, 255);
				sd = new ShapeData(ShapeData.GREEN, ShapeData.NOTHING, ShapeData.NOTHING, ShapeData.NOTHING);
			}
			else if(Color == ShapeData.BLUE){
				SColor = new Scalar(0, 0, 255, 255);
				sd = new ShapeData(ShapeData.BLUE, ShapeData.NOTHING, ShapeData.NOTHING, ShapeData.NOTHING);
			}
			else if(Color == ShapeData.YELLOW){
				SColor = new Scalar(255, 255, 0, 255);
				sd = new ShapeData(ShapeData.YELLOW, ShapeData.NOTHING, ShapeData.NOTHING, ShapeData.NOTHING);
			}
			else{
				SColor = new Scalar(0, 0, 0, 0);
				sd = new ShapeData(ShapeData.NOTHING, ShapeData.NOTHING, ShapeData.NOTHING, ShapeData.NOTHING);
			}

			//Conversion of matrix from BGR to HSV format
			Imgproc.cvtColor(getmImg(), mIntermediateMat, Imgproc.COLOR_BGR2HSV);

			//Gaussian blur
			//Imgproc.GaussianBlur( mIntermediateMat , mIntermediateMat , new Size(9, 9), 1, 1); 

			//Color Filter
			Core.inRange(mIntermediateMat, lower, upper, mIntermediateMat);
			//Kernel Matrix for closing
			Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ERODE, kernelSize, new Point(1,1));
			Mat kerneld = Imgproc.getStructuringElement(Imgproc.MORPH_DILATE, kernelSize, new Point(1,1));
			//Closing in order to suppress parasites
			Imgproc.erode(mIntermediateMat, mIntermediateMat, kernel);
			Imgproc.dilate(mIntermediateMat, mIntermediateMat, kerneld);
			kernel.release();
			kernel = null;

			//Canny filter (used if no color detection)
			//			Mat mGraySubmat = mIntermediateMat.submat(0, getDispHeight(), 0, getDispWidth());
			//			Imgproc.Canny(mGraySubmat, mIntermediateMat, 80, 100);
			//			mGraySubmat.release();
			//			mGraySubmat = null;

			Mat hierarchy = new	Mat(mIntermediateMat.rows(),mIntermediateMat.cols(),CvType.CV_8UC1,new Scalar(0));
			List<MatOfPoint> contours = new ArrayList<MatOfPoint>(100);
			Imgproc.findContours(mIntermediateMat, contours, hierarchy,Imgproc.CHAIN_APPROX_SIMPLE, Imgproc.RETR_LIST);

			if(DEBUG){
				Log.d(TAG,"Size of the list countours: " + contours.size() + "\n");
				LogCat.writeLog(saved_context, "Size of the list countours: " + contours.size() + "\n");
			}


			MatOfPoint2f pt2f,approx;
			int sumX = 0, sumY = 0, nPixel = 0;
			double area,perimeter,metric_c,metric_s,metric_t;
			for( int i = 0; i < contours.size(); i++ ){
				area = Imgproc.contourArea(contours.get(i));
				//Suppress left parasites which should be too little to pass the test. 
				if (area > 100){
					pt2f = new MatOfPoint2f();
					approx = new MatOfPoint2f();
					//Convert each MatOfpoint to MatOfPoint2f
					contours.get(i).convertTo(pt2f, CvType.CV_32FC2);
					List<Point> lp = contours.get(i).toList();
					for(int j = 0 ; j < lp.size(); j++){
						sumX += lp.get(j).x;
						sumY += lp.get(j).y;
						nPixel++;
					}
					area = Math.abs(area);
					perimeter = Math.abs(Imgproc.arcLength(pt2f, true));
					// Determined metric for circle,square and triangle.
					metric_c=area*4*Math.PI/(perimeter*perimeter);
					metric_s=area*16/(perimeter*perimeter);
					metric_t=area*18/(perimeter*perimeter);

					sd.setX(sumX/nPixel);
					sd.setY(sumY/nPixel);

					//Color for the middle point
					Scalar colorPoint = new Scalar(255,255,255);
					//test if it's a circle
					if(metric_c > 0.75f && metric_s > 1.0f && nPixel > 10){
						if(isScreenDisplay()) Core.circle(mImg, new Point(sd.getX(),sd.getY()), 3, colorPoint);
						Log.i("FORM","circle at " + sd.getX() + "x" + sd.getY() + "\n");
						LogCat.writeLog(saved_context, "circle at " + sd.getX() + "x" + sd.getY() + "\n");
						sd.setShape(ShapeData.CIRCLE);
					}
					//test if it's a square
					else if(metric_s > 0.9f && metric_c < 0.8f  && nPixel > 10){
						if(isScreenDisplay()) Core.circle(mImg, new Point(sd.getX(),sd.getY()), 3, colorPoint);
						Log.i("FORM","square at " + sd.getX() + "x" + sd.getY() + "\n");
						LogCat.writeLog(saved_context, "square at " + sd.getX() + "x" + sd.getY() + "\n");
						sd.setShape(ShapeData.SQUARE);
					}
					//test if it's a triangle
					else if(metric_s < 0.8f && metric_c < 0.6f && metric_t > 0.6f && nPixel > 10){
						if(isScreenDisplay()) Core.circle(mImg, new Point(sd.getX(),sd.getY()), 3, colorPoint);
						Log.i("FORM","triangle at " + sd.getX() + "x" + sd.getY() + "\n");
						LogCat.writeLog(saved_context, "triangle at " + sd.getX() + "x" + sd.getY() + "\n");
						sd.setShape(ShapeData.TRIANGLE);
					}
					//it was not recognized
					else{
						Log.i("FORM","nothing \n");
						LogCat.writeLog(saved_context, "nothing\n");
						sd.setShape(ShapeData.NOTHING);
					}

					Imgproc.approxPolyDP(pt2f,approx, 2, true);

					// convert back to MatOfPoint and put it back in the list
					approx.convertTo(contours.get(i), CvType.CV_32S);

					// draw the contour itself
					if(isScreenDisplay()) Imgproc.drawContours(getmImg(), contours, i, SColor, 1);
				}
			}
			//detection shape end
			return sd; 
		}

		//Getter and Setter for the color
		/**
		 * @return Scalar color
		 */
		public Scalar getColor() {
			return Color;
		}

		/**
		 * @param color the color to set
		 */
		public void setColor(Scalar color) {
			Color = color;
		}
	}
	//End of MjpegViewThread
	
	
	/**
	 * Initialization of the view
	 * @param context Context, the context of the activity which wants to display the view 
	 */
	private void init(Context context) {
		holder = getHolder();
		saved_context = context;
		holder.addCallback(this);
		setDispWidth(getWidth());
		setDispHeight(getHeight());
		setThread(new MjpegViewThread(holder, context));
		setFocusable(true);
		overlayPaint = new Paint();
		overlayPaint.setTextAlign(Paint.Align.LEFT);
		overlayPaint.setTextSize(12);
		overlayPaint.setTypeface(Typeface.DEFAULT);
		overlayTextColor = Color.WHITE;
		overlayBackgroundColor = Color.BLACK;
		ovlPos = MjpegView.POSITION_LOWER_RIGHT;
		listOfShapeRed = new LinkedList<ShapeData>();
		listOfShapeBlue = new LinkedList<ShapeData>();
		listOfShapeGreen = new LinkedList<ShapeData>();
		setUpdateConfiguration(true);
		//		listOfShapeYellow = new ArrayList<ShapeData>();
	}

	public void startPlayback() { 
		if(mIn != null) {
			mRun = true;
			getThread().start();    		
		}
	}

	public void resumePlayback() { 
		if(mIn != null) {
			mRun = true;
			SurfaceHolder holder = getHolder();
			holder.addCallback(this);
			setThread(new MjpegViewThread(holder, saved_context));		
			getThread().start();
		}
	}
	public void stopPlayback() { 
		mRun = false;
		boolean retry = true;
		while(retry) {
			try {
				getThread().join();
				retry = false;
			} catch (InterruptedException e) {}
		}
	}

	public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {
		getThread().setSurfaceSize(w, h); 
	}

	public void surfaceDestroyed(SurfaceHolder holder) { 
		surfaceDone = false; 
		stopPlayback(); 
	}

	public MjpegView(Context context) { super(context); init(context); }    
	public MjpegView(Context context, AttributeSet attrs) { 
		super(context, attrs); init(context); 
	}
	public MjpegView(Context context, AttributeSet attrs, boolean DEBUG) { 
		super(context, attrs); init(context);
		this.DEBUG = DEBUG;
	}
	public void surfaceCreated(SurfaceHolder holder) { surfaceDone = true; }
	public void showFps(boolean b) { showFps = b; }
	public void setSource(MjpegInputStream source) { mIn = source; if(!mRun) startPlayback();}
	public void setOverlayPaint(Paint p) { overlayPaint = p; }
	public void setOverlayTextColor(int c) { overlayTextColor = c; }
	public void setOverlayBackgroundColor(int c) { overlayBackgroundColor = c; }
	public void setOverlayPosition(int p) { ovlPos = p; }

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		long eventDuration = event.getEventTime() - event.getDownTime();
		if(event.getAction() == MotionEvent.ACTION_UP && eventDuration > 1000){
			Log.d("TAG", "duration " + eventDuration + "\n");
			if(isScreenDisplay()) setScreenDisplay(false);
			else setScreenDisplay(true);
		}
		return true;
	}

	/**
	 * @return the mRun
	 */
	public boolean ismRun() {
		return mRun;
	}

	/**
	 * @param mRun the mRun to set
	 */
	public void setmRun(boolean mRun) {
		this.mRun = mRun;
	}

	/**
	 * @return the dEBUG
	 */
	public boolean isDEBUG() {
		return DEBUG;
	}

	/**
	 * @param dEBUG the dEBUG to set
	 */
	public void setDEBUG(boolean dEBUG) {
		DEBUG = dEBUG;
	}

	//Getter and setters used for the cameras
	/**
	 * @return the dispWidth
	 */
	public int getDispWidth() {
		return dispWidth;
	}

	/**
	 * @param dispWidth the dispWidth to set
	 */
	public void setDispWidth(int dispWidth) {
		this.dispWidth = dispWidth;
	}

	/**
	 * @return the dispHeight
	 */
	public int getDispHeight() {
		return dispHeight;
	}

	/**
	 * @param dispHeight the dispHeight to set
	 */
	public void setDispHeight(int dispHeight) {
		this.dispHeight = dispHeight;
	}

	/**
	 * @return the thread
	 */
	public MjpegViewThread getThread() {
		return thread;
	}

	/**
	 * @param thread the thread to set
	 */
	public void setThread(MjpegViewThread thread) {
		this.thread = thread;
	}

	/**
	 * @return the mjpeg2
	 */
	public MjpegCamActivity getMjpeg2() {
		return mjpeg2;
	}

	/**
	 * @param mjpeg2 the mjpeg2 to set
	 */
	public void setMjpeg2(MjpegCamActivity mjpeg2) {
		this.mjpeg2 = mjpeg2;
	}

	/**
	 * @return the camId
	 */
	public int getCamId() {
		return camId;
	}

	/**
	 * @param camId the camId to set
	 */
	public void setCamId(int camId) {
		this.camId = camId;
	}

	/**
	 * @return the screenDisplay
	 */
	public boolean isScreenDisplay() {
		return ScreenDisplay;
	}

	/**
	 * @param screenDisplay the screenDisplay to set
	 */
	public void setScreenDisplay(boolean screenDisplay) {
		ScreenDisplay = screenDisplay;
	}

	// Getters and setters for the change of the mode the user want to play
	/**
	 * @return the mODE
	 */
	public String getMODE() {
		return MODE;
	}

	/**
	 * @param mODE the mODE to set
	 */
	public void setMODE(String mODE) {
		MODE = mODE;
	}
	
	// Getters and setters for the list of the different shapes
	/**
	 * @return List<ShapeData> listOfShapeRed
	 */
	public List<ShapeData> getListOfShapeRed() {
		return listOfShapeRed;
	}

	/**
	 * @param List<ShapeData> listOfShapeRed
	 */
	public void setListOfShapeRed(List<ShapeData> listOfShapeRed) {
		this.listOfShapeRed = listOfShapeRed;
	}

	/**
	 * @return List<ShapeData> listOfShapeBlue
	 */
	public List<ShapeData> getListOfShapeBlue() {
		return listOfShapeBlue;
	}

	/**
	 * @param List<ShapeData> listOfShapeBlue
	 */
	public void setListOfShapeBlue(List<ShapeData> listOfShapeBlue) {
		this.listOfShapeBlue = listOfShapeBlue;
	}

	/**
	 * @return List<ShapeData> listOfShapeGreen
	 */
	public List<ShapeData> getListOfShapeGreen() {
		return listOfShapeGreen;
	}

	/**
	 * @param List<ShapeData> listOfShapeGreen
	 */
	public void setListOfShape(List<ShapeData> listOfShapeGreen) {
		this.listOfShapeGreen = listOfShapeGreen;
	}

	//	/**
	//	 * @return the listOfShapeYellow
	//	 */
	//	public List<ShapeData> getListOfShapeYellow() {
	//		return listOfShapeYellow;
	//	}
	//
	//	/**
	//	 * @param listOfShapeYellow the listOfShapeYellow to set
	//	 */
	//	public void setListOfShapeYellow(List<ShapeData> listOfShapeYellow) {
	//		this.listOfShapeYellow = listOfShapeYellow;
	//	}

	/**
	 * @return the updateConfiguration
	 */
	public static boolean isUpdateConfiguration() {
		return updateConfiguration;
	}

	/**
	 * @param updateConfiguration the updateConfiguration to set
	 */
	public static void setUpdateConfiguration(boolean updateConfiguration) {
		MjpegView.updateConfiguration = updateConfiguration;
	}

	/**
	 * @return int[] LEFT Array with the information about the cube and figure : shape and color
	 */
	public int[] getLEFT() {
		return LEFT;
	}

	/**
	 * @return int[] MIDDLE Array with the information about the cube and figure : shape and color
	 */
	public int[] getMIDDLE() {
		return MIDDLE;
	}

	/**
	 * @return int[] RIGHT Array with the information about the cube and figure : shape and color
	 */
	public int[] getRIGHT() {
		return RIGHT;
	}
}
